using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Collections;
using Web.Ajax.Logging;
using Web.Ajax.Data;

namespace Web.Ajax.Controls
{
	[ToolboxData("<{0}:AjaxGrid runat=\"server\"></{0}:AjaxGrid>")]
	[ControlBuilderAttribute(typeof(AjaxGridBuilder))]
	[ParseChildren(false)]
	public class AjaxGrid : AjaxControl
	{
        public AjaxGrid()
        {
            Width=new System.Web.UI.WebControls.Unit("100%");
        }

		protected override void OnInit(EventArgs e)
		{
			Ajax.RegisterMethods<AjaxGrid>(this, g=>GetGridData(null)); 
			//Ajax.RegisterAjaxMethods(this);
			base.OnInit(e);
			if(PopupTitle!=null)
				if(PopupTitle.TitleRight!=null)
					PopupTitle.TitleRight.DisplayClose = false;
            Web.Ajax.Page p=Page as Web.Ajax.Page;
            if (p != null)
            {
				p.RegisterJavascriptFile(Resources.Javascript.AjaxControl);
				p.RegisterStyleSheet(Resources.StyleSheets.AjaxGrid);
                p.RegisterJavascriptFile(Resources.Javascript.AjaxGrid);
                p.RegisterStyleSheet(Resources.StyleSheets.Calendar);
                p.RegisterJavascriptFile(Resources.Javascript.Calendar);
                p.RegisterStyleSheet(Resources.StyleSheets.PopupMenu);
                p.RegisterJavascriptFile(Resources.Javascript.PopupMenu);
            }
		}        

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			if(!string.IsNullOrEmpty(Display)&&PopupTitle!=null&&PopupTitle.TitleRight!=null)
			{
				string[] parts = Display.Split(',');
				StringBuilder html=new StringBuilder("Display: <select class=\"DisplaySelect\" onchange=\""+JavascriptId+".SetRowsPerPage(parseInt(this.value));\">");
				for(int i=0;i<parts.Length;i++)
				{
					string text = parts[i];
					string value = parts[i];
					if (text == "All")
						value = "-1";
					else if (value == "-1" || value == "*")
						text = "All";
					html.Append("<option value=\""+value+"\">"+text+"</option>");
				}
				html.Append("</select>");
				LiteralControl l=new LiteralControl();
				l.Text = html.ToString();
				PopupTitle.TitleRight.Controls.Add(l);
			}            
		}

        private bool showAdd = false;
        public bool ShowAdd
        {
            get
            {
                return showAdd;
            }
            set
            {
                showAdd = value;
            }
        }

        private bool showDelete = false;
        public bool ShowDelete
        {
            get
            {
                return showDelete;
            }
            set
            {
                showDelete = value;
            }
        }

        private bool showSave = false;
        public bool ShowSave
        {
            get
            {
                return showSave;
            }
            set
            {
                showSave = value;
            }
        }


        private bool showSelector = false;
        public bool ShowSelector
        {
            get
            {
                return showSelector;
            }
            set
            {
                showSelector = value;
            }
        }

        private bool showExpander = false;
        public bool ShowExpander
        {
            get
            {
                return showExpander;
            }
            set
            {
                showExpander = value;
            }
        }

        private bool singleSelection = false;
        public bool SingleSelection
        {
            get
            {
                return singleSelection;
            }
            set
            {
                singleSelection = value;
            }
        }

        private string[] selectedRowIds = null;
        public string[] SelectedRowIds
        {
            get
            {
                return selectedRowIds;
            }
            set
            {
                selectedRowIds = value;
            }
        }

        private bool rowClickSelects = false;
        public bool RowClickSelects
        {
            get
            {
                return rowClickSelects;
            }
            set
            {
                rowClickSelects = value;
            }
        }

        private string preLoad = "";
        public string PreLoad
        {
            get
            {
                return preLoad;
            }
            set
            {
                preLoad = value;
            }
        }

        private string idColumn="Id";
        public string IdColumn
        {
            get
            {
                return idColumn;
            }
            set
            {
                idColumn = value;
            }
        }

        private string onRowClick;
        public string OnRowClick
        {
            get
            {
                return onRowClick;
            }
            set
            {
                onRowClick = value;
            }
        }

        private int maxNewRows = 1;
        public int MaxNewRows
        {
            get { return maxNewRows; }
            set { maxNewRows = value; }
        }

        private string editUrl;
        public string EditUrl
        {
            get { return editUrl; }
            set { editUrl = value; }
        }


        /// <summary>
        /// The signature for the static method used to return the grid data.
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public delegate DataTable GetDataMethod(GridInfo GridInfo);
        public GetDataMethod DataMethod;

        //* Todo Remove
		/// <summary>
		/// Sets the static method to use to Get the DataTable. This method must be static, return void and take a string[] as a parameter.
		/// This must be called with a valid method for the AjaxGrid to work.
		/// <b>Signature:</b> public static void GetData(Parameter[] parameters);
		/// </summary>
		/// <param name="t">The Type that contains the datamethod. Do not use the GetType() function, use typeof(#DataType).</param>
		/// <param name="MethodName">The Name of the Method.</param>
		public void SetDataMethod(GetDataMethod BindMethod)
		{
            DataMethod = BindMethod;            
		}
		//*/

		#region SortMethod
		private string SortMethodName = "";
		private Type SortMethodType = null;
        // Not yet implemented.
		public void SetSortMethod(Type t, string MethodName)
		{
			SortMethodType = t;
			SortMethodName = MethodName;
		}
		#endregion

		#region RowsPerPage
		private int rowsPerPage = 10;
		public int RowsPerPage
		{
			get
			{
				return rowsPerPage;
			}
			set
			{
				rowsPerPage = value;
			}
		} 
		#endregion

        #region DisplayHeadings
        private bool displayHeadings=true;
        public bool DisplayHeadings
        {
            get
            {
                return displayHeadings;
            }
            set
            {
                displayHeadings = value;
            }
        }
        #endregion

        #region CustomPaging
        private bool customPaging;
        public bool CustomPaging
        {
            get
			{
                return customPaging;
            }
            set
            {
                customPaging = value;
            }
        }
        #endregion


        #region PageGroupSize
        private int pageGroupSize=10;
        public int PageGroupSize
        {
            get
            {
                return pageGroupSize;
            }
            set
            {
                pageGroupSize = value;
            }
        }
        #endregion

		#region Display
		private string display = "";
        /// <summary>
        /// Setting this property will automatically add a drop down list to the Grid Title (if present)
        /// that will allow the number of rows per page to be changed by the user.
        /// The format is 10,20,30,-1
        /// A value of -1 indicates show all rows on one page.
        /// A Title control must be present including a TitleRight sub control for this
        /// feature to take effect.
        /// </summary>
		public string Display
		{
			get
			{
				return display;
			}
			set
			{
				display = value;
			}
		}
		#endregion


		#region GridColumns
		private GridColumn[] gridColumns;
		public GridColumn[] GridColumns
		{
			get
			{
				if (gridColumns == null)
				{
					List<GridColumn> cols = new List<GridColumn>();
					for (int i = 0; i < Controls.Count; i++)
					{
						if (Controls[i] is GridColumn)
							cols.Add(Controls[i] as GridColumn);
					}
					gridColumns = cols.ToArray();
				}
				return gridColumns;
			}
		}
		#endregion


		#region Sort
		private string sort = "";
		/// <summary>
		/// The Default Sort string for the AjaxGrid. This will get combined with the Order string to generate the full SortExpression.
		/// </summary>
		public string Sort
		{
			get { return sort; }
			set { sort = value; }
		} 
		#endregion

		#region Order
		private string order = "Asc";
		/// <summary>
		/// The Default Order string for the AjaxGrid. This will get combined with the Sort string to generate the full SortExpression.
		/// </summary>
		public string Order
		{
			get { return order; }
			set { order = value; }
		} 
		#endregion

		

		#region Message
		public static string Message
		{
			get
			{
				return (string)HttpContext.Current.Items["AjaxGridMessage"];
			}
			set
			{
				HttpContext.Current.Items["AjaxGridMessage"] = value;
			}
		} 
		#endregion       
		
        private bool? editable;
        public bool Editable
        {
            get
            {
                if (!editable.HasValue)
                {
                    editable = false;
                    GridColumn[] c=GridColumns;
                    for (int i = 0; i < c.Length; i++)
                    {
                        if (c[i].Editable)
                        {
                            editable = true;
                            break;
                        }
                    }
                }
                return editable.Value;
            }
        }


		#region Render
		protected override void Render(HtmlTextWriter writer)
		{
            if (DataMethod == null)
                throw new Exception("DataMethod was not set on Grid: " + JavascriptId + ". A DataMethod must be provided for the Grid to work.");            
            if ((DataMethod.Method.Attributes & MethodAttributes.Static) != MethodAttributes.Static)
                throw new Exception("Failed to Render Grid: " + JavascriptId + ". The Method: " + DataMethod.Method.DeclaringType.AssemblyQualifiedName + " is not static.");

			if(!Visible)
				return;
            string style="";
            string width = Width.ToString();
            style += Page.GetTableWidthStyle(width);

            writer.WriteLine("<table class=\"GridPanel\" id=\"" + JavascriptId + "_Grid\" style=\""+style+"\" cellpadding=\"0\" cellspacing=\"0\">");
            RenderTitle(writer, typeof(AjaxGrid));
			writer.WriteLine("<tr><td id=\"" + JavascriptId + "_Content\" class=\"GridContent\" colspan=\"3\"></td></tr></table>");
			writer.WriteLine("<script type=\"text/javascript\">");
            writer.WriteLine("var " + JavascriptId + " = new AjaxGrid('" + JavascriptId + "'," + RowsPerPage.ToString() + ",0,'" + DataMethod.Method.DeclaringType.AssemblyQualifiedName + "','" + DataMethod.Method.Name + "','" + Sort + "','" + Order + "');");

			var properties = new Hashtable();
			if(ShowSelector)
				properties["ShowSelector"] = ShowSelector;
			if(ShowExpander)
				properties["ShowExpander"] = ShowExpander;
			properties["Columns"] = GridColumns;

			ProcessParameterClientIds();
			properties["Parameters"] = Parameters;

			properties["NoData"] = NoData;
			properties["PreLoad"] = PreLoad;
			properties["DisplayHeadings"] = DisplayHeadings;
			properties["CustomPaging"] = CustomPaging;
			properties["SingleSelection"] = SingleSelection;
			properties["RowClickSelects"] = RowClickSelects;
			properties["PageGroupSize"] = PageGroupSize;
			properties["ShowAdd"] = ShowAdd;
			properties["ShowDelete"] = ShowDelete;
			properties["ShowSave"] = ShowSave;
			properties["Editable"] = Editable;
			properties["MaxNewRows"] = MaxNewRows;

			if (SelectedRowIds!=null)
				properties["SelectedRows"] = SelectedRowIds;

			if (!string.IsNullOrEmpty(OnRowClick))
			{
				OnRowClick = OnRowClick.Contains("(params)") || OnRowClick.Contains("(string)") ? OnRowClick : string.Concat(OnRowClick, "(params)");
                properties["OnRowClick"] = OnRowClick; 
			}
			if (!string.IsNullOrEmpty(IdColumn))
                properties["IdColumn"] = IdColumn; 
            if (!string.IsNullOrEmpty(EditUrl))
                properties["EditUrl"] = EditUrl;
			writer.WriteLine("$.extend(true, " + JavascriptId + ", " + Json.ConvertToJson(properties) + ");");

			//*/

            if(DelayLoading)
                writer.WriteLine(JavascriptId + ".Clear();");
            else
                writer.WriteLine(JavascriptId + ".GetPage(0);");
            RenderJavascriptId(writer);
            writer.WriteLine("</script>");
		} 
		#endregion

		private static readonly string[] SpecialColumns = new string[] { "ParentId", "EnableSelection", "Expand", "Editable", "RowCss" };

		#region GetRowJson
		public static string GetRowJson(DataRow row, GridColumn[] cols)
		{
			//e.g. {Idx:0,a:'p1',b:'1b',c:'1c',d:'1d'}
			StringBuilder json = new StringBuilder();
			json.Append("{");
            bool AddComma = false;
			for (int i = 0; i < cols.Length; i++)
			{
				if (AddComma)
					json.Append(",");
				json.Append(cols[i].GetItemJson(row));
                AddComma = true;
			}
            for(int i=0;i<SpecialColumns.Length;i++)
            {
                string c=SpecialColumns[i];    
                if (row.Table.Columns.Contains(c))
                {
                    if (AddComma)
                        json.Append(",");
                    json.Append("\""+c+"\":"+Json.ConvertToJson(row[c]));
                }
            }
			json.Append("}");
			return json.ToString();
		} 
		#endregion

		#region GetPageJson
		public static string GetPageJson(DataTable dt, GridColumn[] cols, GridInfo gi)
		{
			//e.g.	[{Idx:0,a:'p1',b:'1b',c:'1c',d:'1d'},{Idx:1,a:'2a',b:'2b',c:'2c',d:'2d'},{Idx:2,a:'3a',b:'3b',c:'3c',d:'3d'},{Idx:3,a:'4a',b:'4b',c:'4c',d:'4d'}];
			if (dt == null)
				return Json.ConvertToJson(null);
			StringBuilder json = new StringBuilder();
			json.Append("[");
			int startIdx = gi.Page * gi.RowsPerPage;
            if (gi.CustomPaging)
                startIdx = 0;
			if (startIdx < 0)
				startIdx = 0;
			int endIdx = startIdx + gi.RowsPerPage;
			if (gi.RowsPerPage == -1 || gi.CustomPaging)
				endIdx = dt.Rows.Count;
			for (int i = startIdx; i < endIdx; i++)
			{
				if (i >= dt.Rows.Count)
					break;
				if (i > startIdx)
					json.Append(",");
				json.Append(GetRowJson(dt.Rows[i], cols));
			}
			json.Append("]");
			return json.ToString();
		} 
		#endregion

		#region GetGridData
		/// <summary>
		/// Called from the client through Ajax when the data needs to be updated. Only the data for the current page is 
		/// returned.
		/// </summary>
		/// <param name="DataMethodType"></param>
		/// <param name="DataMethodName">The Method to call to retrieve the DataTable.</param>
		/// <param name="cols">The AjaxGrid Columns.</param>
		/// <param name="Sort">The Sort string.</param>
		/// <param name="Order">The Sort Order.</param>
		/// <param name="Page">The Current Page Index.</param>
		/// <param name="RowsPerPage">The Number of Rows per Page.</param>
		/// <param name="parameters">The Parameters to pass to the DataMethod.</param>
		/// <returns></returns>
		[Ajax]
		public static GridResponse GetGridData(GridRequest request)
		{
            GridInfo gi = request.GridInfo;
            gi.GenerateSchema();
			Type Type = System.Type.GetType(request.DataMethodType);
			if (Type == null)
				throw new Exception("AjaxGrid: Invalid Type.");
			MethodInfo Method = Type.GetMethod(request.DataMethodName);
			if (gi.Parameters == null)
				gi.Parameters = new Parameter[0];
			object o = null;
			string err = "DataMethod: " + request.DataMethodName+"() ";
			bool GetDataCallSuccessful = false;
            bool ColumnsInferred = false;
			try
			{
				o = Method.Invoke(null, new object[] { gi });
				if (o == null || !(o is DataTable))		//Invalid DataTable
					throw new Exception("It did not return a valid DataTable.");
				GetDataCallSuccessful = true;
			}
			catch (Exception e)
			{
				err += "call failed. ";
				if (e.InnerException != null)
					err += e.InnerException.Message;
				else
					err += e.Message;
				Log.Error(err,e);
				Message = err;
			}
			int TotalCount = gi.TotalCount;
			string PageData=Json.ConvertToJson(null);
			if(GetDataCallSuccessful)
			{
				DataTable dt = (DataTable)o;
				DataView dv = new DataView(dt);
				if (gi.TotalCount > dt.Rows.Count)
					gi.CustomPaging = true;
				if (!gi.CustomPaging&&!string.IsNullOrEmpty(gi.Sort))
					dv.Sort = gi.Sort + " " + gi.Order;
				DataTable dtSorted = dv.ToTable();
                if(!gi.CustomPaging)
                    TotalCount = dtSorted.Rows.Count;
				int PageCount = 1;
				if (gi.RowsPerPage != -1)
				{
					PageCount = TotalCount / gi.RowsPerPage;
					if ((TotalCount % gi.RowsPerPage) > 0)
						PageCount++;
				}
				if (gi.Page > 0 && gi.Page > (PageCount - 1))		//Make sure the Current Page is never greater that the PageCount
					gi.Page = (PageCount - 1);
				try
				{
                    ColumnsInferred=InferColsIfNone(ref gi.Columns, dt, gi);
					PageData = GetPageJson(dtSorted, gi.Columns, gi);
				}
				catch(Exception e)
				{
					err = "GetPageJson() called Failed. "+e.Message;
					Log.Error(err,e);
					Message = err;
				}
			}
            GridResponse response = new GridResponse();
            response.TotalCount = TotalCount;
            response.Columns = ColumnsInferred||gi.ColumnsChanged?gi.Columns:null;
            response.Message = Message;
            response.PageData = PageData;
            response.ClearSelectedIds = gi.ClearSelectedIds;
            response.ClearModifiedRows = gi.ClearModifiedRows;
            response.OnComplete = gi.OnComplete;
			response.SetSelectedIds=gi.SetSelectedIds;
			response.SelectedIds = gi.SelectedRowIds;
            return response;            
		}
		#endregion

        public static bool InferColsIfNone(ref GridColumn[] cols, DataTable dt, GridInfo gi)
        {
            if (cols == null || dt == null || dt.Columns==null)
                return false;
            if (cols.Length > 0)
                return false;
            List<GridColumn> newcols = new List<GridColumn>();
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                GridColumn c = new GridColumn();
                c.Name = dt.Columns[i].ColumnName;
                c.Binding = dt.Columns[i].ColumnName;
                if(!gi.CustomPaging)
                    c.Sort = dt.Columns[i].ColumnName;
                newcols.Add(c);
            }
            cols = newcols.ToArray();
            return true;
        }
    }

    #region AjaxGridBuilder
    /// <summary>
    /// The Control Builder for the AjaxGrid class.
    /// </summary>
    internal class AjaxGridBuilder : ControlBuilder
	{
		public override System.Type GetChildControlType(string tagName, System.Collections.IDictionary attribs)
		{
			tagName = tagName.ToLower();
			if (tagName == "title")
				return typeof(PopupTitle);
			if (tagName == "column")
				return typeof(GridColumn);
			if (tagName == "parameter")
				return typeof(Parameter);
			return null;
		}
	}
	#endregion


    public class GridRequest : AjaxControlRequest
    {
        public GridInfo GridInfo;
    }

    public class GridResponse : AjaxControlResponse
    {
        public int TotalCount;
        public GridColumn[] Columns;
        public bool ClearSelectedIds;
        public bool ClearModifiedRows;
		public bool SetSelectedIds;
		public string[] SelectedIds;
        public string PageData;
    }

    public enum Action
    {
        None,
        Delete,
        Save,
        Validate
    }

    public class GridInfo : ControlInfo
    {        
        public string[] SelectedRowIds;
        public int Page;
        public int RowsPerPage;
        public bool CustomPaging;
        public int TotalCount;
		public bool SetSelectedIds;
        public bool ClearSelectedIds;
        public bool ClearModifiedRows;
		public bool ColumnsChanged;
        public GridColumn[] Columns;
        public string Sort;
        public string Order;
        public Hashtable[] ModifiedRows;
        public Hashtable[] NewRows;
        public DataTable DataSchema;
		public Data.Format.Target? FormatTarget
		{
			get
			{
				return (Data.Format.Target?)GetEnumParameter("FormatTarget", typeof(Data.Format.Target));
			}
		}

		public string SortExpression
		{
			get
			{
				if(!string.IsNullOrEmpty(Sort))
					return Sort + " " + Order;
				return null;
			}
		}


        public void GenerateSchema()
        {
            if (Columns == null)
                return;
            DataSchema = new DataTable();
            for (int i = 0; i < Columns.Length; i++)
            {
                GridColumn c = Columns[i];
                if (string.IsNullOrEmpty(c.Binding))
                    continue;
                Type t = null;
                if (!string.IsNullOrEmpty(c.Type))
                    t = Type.GetType(c.Type, false);
                if (t == null)
                    t = typeof(string);
                DataSchema.Columns.Add(Columns[i].Binding, t);
            }
        }

		public void NewTable()
		{
			DataSchema = new DataTable();
		}

		public void AddColumn(string name, Type type)
		{
			if (DataSchema == null)
				DataSchema = new DataTable();
			DataSchema.Columns.Add(name, type);
		}

		public void AddColumn(string name)
		{
			AddColumn(name, typeof(string));
		}

		public void AddColumns(params string[] cl)
		{
			cl.Each(c => AddColumn(c));
		}

		public void AddRow(params object[] l)
		{
			if (l == null)
				return;
			DataSchema.Rows.Add(l);
		}

		public DataTable GetTable()
		{
			return DataSchema;
		}

    }
}
